#include "MyStrategy.h"

#define _USE_MATH_DEFINES
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <ctime>
#include <limits>

Logger::Logger():
    std::ofstream("out.log", std::ios_base::out | std::ios_base::trunc)
{

}

Logger& Logger::instance()
{
    static Logger logger;
    return logger;
}

template<typename C, typename T>
bool contains(const C& container, const T& val)
{
    return container.end() != container.find(val);
}

struct TroopPred
{
    int maxIndex;
    TroopPred():maxIndex(0) {}
    bool operator()(const model::Trooper& t)
    {
        if(t.isTeammate())
        {
            maxIndex = std::max(maxIndex, t.getTeammateIndex());
            return true;
        }
        return false;
    }
};

void MyStrategyImpl::firstInit(const model::Trooper& self, const model::World& world, const model::Game& game)
{
    mWidth  = world.getWidth();
    mHeight = world.getHeight();
    mWorldCells = world.getCells();

    const int cellsCount = mWidth * mHeight;
    if(0 == mCells)
    {
        mCells = new Cell[cellsCount];
    }
    if(self.getX() < mWidth / 2)
    {
        if(self.getY() < mHeight / 2)
        {
            mStartCorner = 1;
        }
        else
        {
            mStartCorner = 4;
        }
    }
    else
    {
        if(self.getY() < mHeight / 2)
        {
            mStartCorner = 2;
        }
        else
        {
            mStartCorner = 3;
        }
    }

    const int maxShootingRange = 10;
    const int sqMaxShootingRange = maxShootingRange * maxShootingRange;
    for(int y = 0; y < mHeight; ++y)
    {
        for(int x = 0; x < mWidth; ++x)
        {
            Cell& c = getCell(x, y);
            c.resetUnknownDanger();
            if(!isCellPassable(x,y))
            {
                continue;
            }
            const int minX = std::max(0          , x - maxShootingRange);
            const int maxX = std::min(mWidth - 1 , x + maxShootingRange);
            const int minY = std::max(0          , y - maxShootingRange);
            const int maxY = std::min(mHeight - 1, y + maxShootingRange);
            int a[StanceTypesCount] = {0};
            int d[StanceTypesCount] = {0};
            for(int y1 = minY; y1 <= maxY; ++y1)
            {
                for(int x1 = minX; x1 < maxX; ++x1)
                {
                    if(x1 == x && y1 == y)
                    {
                        continue;
                    }
                    int dx = x1 - x;
                    int dy = y1 - y;
                    if((dx * dx + dy * dy) > sqMaxShootingRange)
                    {
                        continue;
                    }

                    for(int st = 0; st < StanceTypesCount; ++st)
                    {
                        if(world.isVisible(maxShootingRange,
                                           x1, y1, model::STANDING,
                                           x , y , (model::TrooperStance)st))
                        {
                            ++d[st];
                        }
                        if(world.isVisible(maxShootingRange,
                                           x , y , (model::TrooperStance)st,
                                           x1, y1, model::STANDING))
                        {
                            ++a[st];
                        }
                    }

                }
            }
            for(int st = 0; st < StanceTypesCount; ++st)
            {
                c.unknownDanger[st] = float(a[st]) / float(d[st]);
                c.maxUnknownDanger = std::max(c.maxUnknownDanger, c.unknownDanger[st]);
            }
        }
    }

//    LOG() << "World unknown danger:" << std::endl;
//    LOG().precision(4);
//    for(int y = 0; y < mHeight; ++y)
//    {
//        for(int x = 0; x < mWidth; ++x)
//        {
//            if(isCellPassable(x,y))
//            {
//                LOG() << getCell(x,y).maxUnknownDanger;
//            }
//            else
//            {
//                LOG() << "xxxx";
//            }
//            LOG() << "\t";
//        }
//        LOG() << std::endl;
//    }
}

void MyStrategyImpl::updateMap(const model::Trooper& self, const model::World &world, const model::Game &game)
{
    int targetX = -1;
    int targetY = -1;

//    LOG() << "move index: " << world.getMoveIndex() << std::endl;
//    LOG() << "Is disp req: " << mDispositionReq << std::endl;
//    for(const auto& p : world.getPlayers())
//    {
//        LOG() << "Player approx pos: " << p.getApproximateX() << " " << p.getApproximateY() << std::endl;
//    }

    const int edgeOffset = 3;
    if(!mDispositionReq)
    {
        switch(mMapUpdateCount % 5)
        {
            case 0:
                targetX = mWidth / 2;
                targetY = mHeight / 2;
                break;
            case 1:
                targetX = edgeOffset;
                targetY = edgeOffset;
                break;
            case 2:
                targetX = mWidth - 1 - edgeOffset;
                targetY = edgeOffset;
                break;
            case 3:
                targetX = mWidth - 1 - edgeOffset;
                targetY = mHeight - 1 - edgeOffset;
                break;
            case 4:
                targetX = edgeOffset;
                targetY = mHeight - 1 - edgeOffset;
                break;
            default:
                break;
        }
    }
    else
    {
        int teamX = self.getX();
        int teamY = self.getY();
        for(const auto& t : world.getTroopers())
        {
            if(t.isTeammate())
            {
                teamX = (teamX + t.getX()) / 2;
                teamY = (teamY + t.getY()) / 2;
            }
        }

        for(const auto& p : world.getPlayers())
        {
            if(p.getId() == self.getPlayerId() ||
               -1 == p.getApproximateX()) continue;
            if(-1 == targetX)
            {
                targetX = p.getApproximateX();
                targetY = p.getApproximateY();
            }
            else
            {
                const int xTargetRange = teamX - targetX;
                const int yTargetRange = teamY - targetY;
                const int xPlayerRange = teamX - p.getApproximateX();
                const int yPlayerRange = teamY - p.getApproximateY();
                if((xPlayerRange * xPlayerRange + yPlayerRange * yPlayerRange) <
                   (xTargetRange * xTargetRange + yTargetRange * yTargetRange))
                {
                    targetX = p.getApproximateX();
                    targetY = p.getApproximateY();
                }
            }
        }
    }

    //find nearest empty cell
    for(int i = 0; !isCellPassable(targetX, targetY); ++i)
    {
        const int dist = i / 2 + 1;
        int xInc = 0;
        int yInc = 0;
        switch(i % 4)
        {
            case 0:
                xInc = 1;
                yInc = 0;
                break;
            case 1:
                xInc = 0;
                yInc = 1;
                break;
            case 2:
                xInc = -1;
                yInc = 0;
                break;
            case 3:
                xInc = 0;
                yInc = -1;
                break;
            default:
                break;
        }

        for(int j = 0; (j < dist) && !isCellPassable(targetX, targetY); ++j)
        {
            targetX += xInc;
            targetY += yInc;
        }
    }

    fillMap(targetX, targetY, MaxPathWeight, PathWeightInc, 4);

//    LOG() << "World:" << std::endl;
//    for(int y = 0; y < mHeight; ++y)
//    {
//        for(int x = 0; x < mWidth; ++x)
//        {
//            if(-1 == getCell(x,y).weightUpdateIteration)
//            {
//                LOG() << "xxxx";
//            }
//            else
//            {
////                LOG() << getCell(x,y).unknownDanger[model::STANDING] << " "
////                      << getCell(x,y).unknownDanger[model::KNEELING] << " "
////                      << getCell(x,y).unknownDanger[model::PRONE];
//                LOG() << getCell(x,y).weight;
//            }
//            LOG() << '\t';
//        }
//        LOG() << std::endl;
//    }

    ++mMapUpdateCount;
}

void MyStrategyImpl::fillMap(int startX, int startY, int initialVal, int valDec, int skipDec)
{
    for(int y = 0; y < mHeight; ++y)
    {
        for(int x = 0; x < mWidth; ++x)
        {
            getCell(x, y).setWeight(0, -1);
        }
    }
    int cellsUpdated;
    int currentValue = initialVal;
    getCell(startX, startY).setWeight(currentValue, 0);
    int i = 0;
    do
    {
        ++i;
        int currentValueDec = 0;
        cellsUpdated = 0;
        if(skipDec > 0)
        {
            --skipDec;
        }
        else
        {
            currentValueDec = valDec;
        }

        for(int y = 0; y < mHeight; ++y)
        {
            for(int x = 0; x < mWidth; ++x)
            {
                Cell& c = getCell(x, y);
                if(-1 != c.weightUpdateIteration ||
                   !isCellPassable(x, y))
                {
                    continue;
                }

                const int newValue = currentValue - currentValueDec;
                if(x > 0)
                {
                    Cell& c1 = getCell(x - 1, y);
                    if(-1 != c1.weightUpdateIteration && c1.weightUpdateIteration < i)
                    {
                        c.setWeight(newValue,i);
                        ++cellsUpdated;
                        continue;
                    }
                }
                if(x < (mWidth - 1))
                {
                    Cell& c1 = getCell(x + 1, y);
                    if(-1 != c1.weightUpdateIteration && c1.weightUpdateIteration < i)
                    {
                        c.setWeight(newValue,i);
                        ++cellsUpdated;
                        continue;
                    }
                }
                if(y > 0)
                {
                    Cell& c1 = getCell(x, y - 1);
                    if(-1 != c1.weightUpdateIteration && c1.weightUpdateIteration < i)
                    {
                        c.setWeight(newValue,i);
                        ++cellsUpdated;
                        continue;
                    }
                }
                if(y < (mHeight - 1))
                {
                    Cell& c1 = getCell(x, y + 1);
                    if(-1 != c1.weightUpdateIteration && c1.weightUpdateIteration < i)
                    {
                        c.setWeight(newValue,i);
                        ++cellsUpdated;
                        continue;
                    }
                }
            }
        }
        currentValue -= currentValueDec;
    }
    while(cellsUpdated > 0);
}

void MyStrategyImpl::updateDanger(const model::Trooper& self, const model::World& world, const model::Game& game)
{
    std::vector<model::Trooper> enemyTroops = world.getTroopers();

    TroopPred pred;
    const auto it = std::remove_if(enemyTroops.begin(), enemyTroops.end(), pred);
    std::vector<model::Trooper> myTroops(it, enemyTroops.end());
    myTroops.push_back(self);
    enemyTroops.erase(it, enemyTroops.end());

    int teamX = -1;
    int teamY = -1;
    int maxAccessRange = 0;
    for(const auto& t : myTroops)
    {
        if(-1 == teamX)
        {
            teamX = t.getX();
            teamY = t.getY();
        }
        else
        {
            teamX = teamX + (t.getX() - teamX) / 2;
            teamY = teamY + (t.getY() - teamY) / 2;
        }
        const int accessRange = t.getInitialActionPoints() / game.getStandingMoveCost();
        maxAccessRange = std::max(maxAccessRange, accessRange);
        const int minX = std::max(0          , t.getX() - accessRange);
        const int maxX = std::min(mWidth - 1 , t.getX() + accessRange);
        const int minY = std::max(0          , t.getY() - accessRange);
        const int maxY = std::min(mHeight - 1, t.getY() + accessRange);
        for(int y = minY; y <= maxY; ++y)
        {
            for(int x = minX; x <= maxX; ++x)
            {
                if(!isCellPassable(x,y))
                {
                    continue;
                }
                Cell& c = getCell(x, y);
                c.lastUpdate = mUpdateCount;
            }
        }
    }

    for(int y = 0; y < mHeight; ++y)
    {
        for(int x = 0; x < mWidth; ++x)
        {
            Cell& c = getCell(x, y);

            if(c.lastUpdate != mUpdateCount)
            {
                continue;
            }

            int newDanger = world.getMoveIndex() * DangerDecay;
            for(const auto& t : enemyTroops)
            {
                for(int st = 0; st < StanceTypesCount; ++st)
                {
                    int dmg = 0;
                    if(world.isVisible(t.getShootingRange(), t.getX(), t.getY(), t.getStance(),
                                       x, y, (model::TrooperStance)st))
                    {
                        dmg += calcDamage(t, t.getStance());
                    }
                    else if(world.isVisible(t.getVisionRange(), t.getX(), t.getY(), t.getStance(),
                                            x, y, (model::TrooperStance)st))
                    {
                        dmg += calcDamage(t, t.getStance()) / 2;
                    }
                    newDanger += dmg;
                }
            }
            c.enemyDanger = std::max(newDanger, c.enemyDanger);

            c.teamWeight = 0;
            const double teamMaxDist = 6.0;
            const int teamMaxBonus = 200;
            const int xRange = x - teamX;
            const int yRange = y - teamY;
            const double dist = std::sqrt((double)(xRange * xRange + yRange * yRange));
            if(dist < teamMaxDist)
            {
                const double coeff = (dist / teamMaxDist);
                c.teamWeight = teamMaxBonus * (1.0 - coeff * coeff * coeff);
            }
        }
    }
/*
    std::ofstream str("out.log", std::ios_base::out | std::ios_base::app);
    str << "Team pos: " << teamX << " " << teamY << std::endl;
    str << "World unknown danger:" << std::endl;
    str.setf(std::ios::showpos);
    for(int y = 0; y < mHeight; ++y)
    {
        for(int x = 0; x < mWidth; ++x)
        {
            str << getCell(x,y).unknownDanger[model::STANDING];
            str << '\t';
        }
        str << std::endl;
    }
*/
    ++mUpdateCount;
}

int MyStrategyImpl::calcDamage(const model::Trooper& t, model::TrooperStance st)
{
    return t.getDamage(st);
}

double MyStrategyImpl::calcShootingRange(const model::Trooper& t, const model::Game &/*g*/)
{
//    if(model::SNIPER == t.getType())
//    {
//        switch(t.getStance())
//        {
//            case model::STANDING:
//                return t.getShootingRange() + g.getSniperStandingShootingRangeBonus();
//            case model::KNEELING:
//                return t.getShootingRange() + g.getSniperKneelingShootingRangeBonus();
//            case model::PRONE:
//                return t.getShootingRange() + g.getSniperProneShootingRangeBonus();
//            default:
//                break;
//        }
//    }
    return t.getShootingRange();
}

int MyStrategyImpl::calcMoveCost(const model::Game& game, model::TrooperStance st)
{
    switch(st)
    {
        case model::STANDING: return game.getStandingMoveCost();
        case model::KNEELING: return game.getKneelingMoveCost();
        case model::PRONE:    return game.getProneMoveCost();
        default: return 0;
    }
}

int MyStrategyImpl::calcTrooperStrength(const model::Trooper& t)
{
    return /*t.getVisionRange() * 10*/ + t.getHitpoints() * 9 + t.getProneDamage();
}

int MyStrategyImpl::calcRetreatWeight(int x, int y, model::TrooperStance stance) const
{
    return getCell(x, y).unknownDanger[stance] * UnknownDangerMult;
}

int MyStrategyImpl::calcAdvanceWeight(int x, int y, model::TrooperStance stance) const
{
    return getCell(x, y).weight + getCell(x, y).unknownDanger[stance] * UnknownDangerMult;
}

bool MyStrategyImpl::isNear(int x1, int y1, int x2, int y2)
{
    return ((1 == std::abs(x1 - x2)) && y1 == y2) ||
           ((1 == std::abs(y1 - y2)) && x1 == x2);
}

bool MyStrategyImpl::isCellPassable(int x, int y) const
{
    if(x < 0 || x >= mWidth)
    {
        return false;
    }
    if(y < 0 || y >= mHeight)
    {
        return false;
    }
    return model::FREE == mWorldCells[x][y];
}

int MyStrategyImpl::getNextTrooper(int t) const
{
    MoveOrderType::size_type i;
    for(i = 0; i < mMoveOrder.size(); ++i)
    {
        if(t == mMoveOrder[i]) break;
    }
    if((i + 1) == mMoveOrder.size()) i = -1;
    return mMoveOrder[i + 1];
}

bool MyStrategyImpl::isInFight(const model::Trooper& self, const TroopersVec& troopers)
{
    if(self.getHitpoints() < self.getMaximalHitpoints()) return true;
    for(const auto& t : troopers)
    {
        if(t.getHitpoints() > 0)
        {
            if(!t.isTeammate() || t.getHitpoints() < t.getMaximalHitpoints()) return true;
        }
    }
    return false;
}

int MyStrategyImpl::getTeamSize(const model::Trooper& self, const TroopersVec& troopers)
{
    int ret = 1;
    for(const auto& t : troopers)
    {
        if(t.getHitpoints() > 0 &&
           t.isTeammate() &&
           t.getTeammateIndex() != self.getTeammateIndex())
        {
            ++ret;
        }
    }
    return ret;
}

//template<int N, typename T>
//T rotateBits(T val)
//{
//    return (val << N) | (val >> (sizeof(T) * 8 - N));
//}

// From boost
template<typename T>
void hash_combine(size_t & seed, const T& v)
{
    seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}

size_t MyStrategyImpl::calcHash(
        const model::Trooper& self,
        const TroopersVec& troopers,
        const BonusesVec& bonuses)
{
    size_t result = 0;
    hash_combine(result, self.getHitpoints());
    hash_combine(result, self.getActionPoints());
    hash_combine(result, self.getX());
    hash_combine(result, self.getY());

    for(const auto& t : troopers)
    {
        hash_combine(result, t.getHitpoints());
    }

    for(const auto& b : bonuses)
    {
        hash_combine(result, b.getType());
    }
    return result;
}

template<typename F>
void MyStrategyImpl::applyTask(const Task& ac,
            const model::Trooper& self,
            const BonusesVec& bonuses,
            const TroopersVec& troopers,
            const model::World& world,
            const model::Game& game,
            F functor)
{
    model::Trooper newSelf = self;
    switch(ac.action)
    {
        case model::THROW_GRENADE:
        {
            TroopersVec newTroopers = troopers;
            const int moveCost = game.getGrenadeThrowCost();
            const int newAp = self.getActionPoints() - moveCost;
            const int newSelfHp = isNear(self.getX(), self.getY(), ac.x, ac.y) ?
                        self.getHitpoints() - game.getGrenadeCollateralDamage() :
                        self.getHitpoints();
            model::Trooper& oldTrooper = newTroopers[ac.trooperInd];
            const int dmg = game.getGrenadeDirectDamage();
            oldTrooper.hitpoints -= dmg;
            for(int j = 0; j < troopers.size(); ++j)
            {
                model::Trooper& t = newTroopers[j];
                if(isNear(t.getX(), t.getY(), ac.x, ac.y))
                {
                    t.hitpoints -= game.getGrenadeCollateralDamage();
                }
            }
            newSelf.holdingGrenade = false;
            newSelf.hitpoints = newSelfHp;
            newSelf.actionPoints = newAp;
            functor(newSelf, bonuses, newTroopers, world, game, false);
            break;
        }
        case model::EAT_FIELD_RATION:
        {
            const int restore = game.getFieldRationBonusActionPoints() - game.getFieldRationEatCost();
            const int newAp = std::min(self.getActionPoints() + restore, self.getInitialActionPoints());
            newSelf.holdingFieldRation = false;
            newSelf.actionPoints = newAp;
            functor(newSelf, bonuses, troopers, world, game, false);
            break;
        }
        case model::USE_MEDIKIT:
        case model::HEAL:
        {
            const bool isMedic      = (model::HEAL == ac.action);
            const int  count        = ac.count;
            const bool medikitState = isMedic ? self.isHoldingMedikit() : false;
            const int  moveCost     = isMedic ? game.getFieldMedicHealCost() * count: game.getMedikitUseCost();
            newSelf.holdingMedikit = medikitState;
            newSelf.actionPoints -= moveCost;
            if(ac.trooperInd >= 0)
            {
                TroopersVec newTroopers = troopers;
                model::Trooper& oldTrooper = newTroopers[ac.trooperInd];
                const int restore = std::min(oldTrooper.getMaximalHitpoints() - oldTrooper.getHitpoints(),
                                             isMedic ? game.getFieldMedicHealBonusHitpoints() * count : game.getMedikitBonusHitpoints());
                oldTrooper.hitpoints += restore;
                functor(newSelf, bonuses, newTroopers, world, game, false);
            }
            else
            {
                const int restore = std::min(self.getMaximalHitpoints() - self.getHitpoints(),
                                             isMedic ? game.getFieldMedicHealSelfBonusHitpoints() * count: game.getMedikitHealSelfBonusHitpoints());
                newSelf.hitpoints += restore;
                functor(newSelf, bonuses, troopers, world, game, false);
            }
            break;
        }
        case model::SHOOT:
        {
            TroopersVec newTroopers = troopers;
            const int count = ac.count;
            const int moveCost = self.getShootCost() * count;
            const int newAp = self.getActionPoints() - moveCost;
            const int dmg = calcDamage(self, self.getStance()) * count;
            model::Trooper& oldTrooper = newTroopers[ac.trooperInd];
            const int newHp = oldTrooper.getHitpoints() - dmg;
            oldTrooper.hitpoints = newHp;
            newSelf.actionPoints = newAp;
            functor(newSelf, bonuses, newTroopers, world, game, false);
            break;
        }
        case model::MOVE:
        {
            const int count = ac.count;
            const int moveCost = calcMoveCost(game, self.getStance()) * count;
            const int newAp = self.getActionPoints() - moveCost;
            newSelf.x = ac.x;
            newSelf.y = ac.y;
            newSelf.actionPoints = newAp;
            if(-1 != ac.bonusInd)
            {
                BonusesVec newBonuses = bonuses;
                newBonuses[ac.bonusInd].type = model::UNKNOWN_BONUS;
                functor(newSelf, newBonuses, troopers, world, game, false);
            }
            else
            {
                functor(newSelf, bonuses, troopers, world, game, false);
            }
            break;
        }
        case model::RAISE_STANCE:
        case model::LOWER_STANCE:
        {
            const int moveCost = game.getStanceChangeCost();
            const int newAp = self.getActionPoints() - moveCost;
            const int inc = (model::RAISE_STANCE == ac.action) ? 1 : -1;
            const model::TrooperStance newStance = (model::TrooperStance)(self.getStance() + inc);
            newSelf.stance = newStance;
            newSelf.actionPoints = newAp;
            if(model::SNIPER == newSelf.getType())
            {
                newSelf.shootingRange = 10.0;
                switch(newStance)
                {
                    case model::STANDING:
                        newSelf.shootingRange += game.getSniperStandingShootingRangeBonus();
                        break;
                    case model::KNEELING:
                        newSelf.shootingRange += game.getSniperKneelingShootingRangeBonus();
                        break;
                    case model::PRONE:
                        newSelf.shootingRange += game.getSniperProneShootingRangeBonus();
                        break;
                    default:
                        break;
                }
            }
            functor(newSelf, bonuses, troopers, world, game, false);
            break;
        }
        case model::REQUEST_ENEMY_DISPOSITION:
        {
            const int newAp = self.getActionPoints() - game.getCommanderRequestEnemyDispositionCost();
            newSelf.actionPoints = newAp;
            functor(newSelf, bonuses, troopers, world, game, false);
            break;
        }
        case model::END_TURN:
        {
            functor(newSelf, bonuses, troopers, world, game, true);
            break;
        }
        default:
            exit(123);
            break;
    }
}

template<typename F>
void MyStrategyImpl::applyTasks(const TaskVecType& tasks,
        const model::Trooper& self,
        const BonusesVec& bonuses,
        const TroopersVec& troopers,
        const model::World& world,
        const model::Game& game,
        F functor,
        TaskVecType::size_type firstTask)
{
    if(firstTask >= tasks.size())
    {
        functor(self, bonuses, troopers, world, game);
        return;
    }
    const Task& ac = tasks[tasks.size() - firstTask - 1];
    auto l = [&](const model::Trooper& self1,
                 const BonusesVec &bonuses1,
                 const TroopersVec &troopers1,
                 const model::World& world1,
                 const model::Game& game1,
                 bool isEnd)
    {
        if(isEnd)
        {
            functor(self1, bonuses1, troopers1, world1, game1);
        }
        else
        {
            applyTasks(tasks, self1, bonuses1, troopers1, world1, game1, functor, firstTask + 1);
        }
    };
    applyTask(ac, self, bonuses, troopers, world, game, l);
}

class MyStrategyImpl::PathHelper
{
    static const int MaxRange = 11;
    static const int FieldSize = MaxRange * 2 + 1;
    const MyStrategyImpl* mMyStategy;
    const int mXoff;
    const int mYoff;
    int mField[FieldSize][FieldSize];

    void fromLocal(int lx, int ly, int& gx, int& gy) const
    {
        gx = mXoff + lx - MaxRange;
        gy = mYoff + ly - MaxRange;
    }

    void toLocal(int gx, int gy, int& lx, int& ly) const
    {
        lx = gx - mXoff + MaxRange;
        ly = gy - mYoff + MaxRange;
    }

public:
    PathHelper(const MyStrategyImpl* myStrategy, int ox, int oy, int maxRange,
               const MyStrategyImpl::TroopersVec& troopers = MyStrategyImpl::TroopersVec()):
        mMyStategy(myStrategy),
        mXoff(ox),
        mYoff(oy)
    {
        if(maxRange >= MaxRange)
        {
            exit(1234);
        }
        const size_t size = sizeof(mField) / sizeof(int);
        bool isPassable[FieldSize][FieldSize];
        for(int y = 0; y < FieldSize; ++y)
        {
            for(int x = 0; x < FieldSize; ++x)
            {
                int gx, gy;
                fromLocal(x, y, gx, gy);
                isPassable[x][y] = mMyStategy->isCellPassable(gx, gy);
            }
        }

        for(int i = 0; i < troopers.size(); ++i)
        {
            const model::Trooper& t = troopers[i];
            if(t.getHitpoints() <= 0) continue;
            int lx, ly;
            toLocal(t.getX(), t.getY(), lx, ly);
            if(lx >= 0 && lx < FieldSize &&
               ly >= 0 && ly < FieldSize)
            {
                isPassable[lx][ly] = false;
            }
        }

        std::memset(&mField[0][0], 0, size * sizeof(mField[0][0]));
        mField[MaxRange][MaxRange] = 1;
        int curr = 2;
        for(int i = 0; i < maxRange; ++i, ++curr)
        {
//            LOG() << std::endl;
//            for(int k = 0; k < FieldSize; ++k)
//            {
//                for(int j = 0; j < FieldSize; ++j)
//                {
//                    if(isPassable[j][k])
//                    {
//                        LOG() << mField[j][k];
//                    }
//                    else
//                    {
//                        LOG() << "x";
//                    }
//                    LOG() << "\t";
//                }
//                LOG() << std::endl;
//            }
            for(int y = 0; y < FieldSize; ++y)
            {
                for(int x = 0; x < FieldSize; ++x)
                {
                    if(!isPassable[x][y] ||
                       0 != mField[x][y])
                    {
                        continue;
                    }

                    if(x > 0)
                    {
                        const int val = mField[x - 1][y];
                        if(val == (curr - 1))
                        {
                            mField[x][y] = curr;
                            continue;
                        }
                    }
                    if(x < (FieldSize - 1))
                    {
                        const int val = mField[x + 1][y];
                        if(val == (curr - 1))
                        {
                            mField[x][y] = curr;
                            continue;
                        }
                    }
                    if(y > 0)
                    {
                        const int val = mField[x][y - 1];
                        if(val == (curr - 1))
                        {
                            mField[x][y] = curr;
                            continue;
                        }
                    }
                    if(y < (FieldSize - 1))
                    {
                        const int val = mField[x][y + 1];
                        if(val == (curr - 1))
                        {
                            mField[x][y] = curr;
                            continue;
                        }
                    }
                }
            }
        }
    }

    int getDistance(int gx, int gy) const
    {
        int lx, ly;
        toLocal(gx, gy, lx, ly);
        if(lx < 0 || lx >= FieldSize ||
           ly < 0 || ly >= FieldSize) return -1;
        return mField[lx][ly] - 1;
    }

    model::Direction getDirection(int x, int y) const
    {
//        LOG() << "getDirection: " << x << " " << y << std::endl;
//        LOG() << "center: " << mXoff << " " << mYoff << std::endl;
//        for(int k = 0; k < FieldSize; ++k)
//        {
//            for(int j = 0; j < FieldSize; ++j)
//            {
//                LOG() << mField[j][k] << "\t";
//            }
//            LOG() << std::endl;
//        }
        int nextX, nextY;
        toLocal(x, y, nextX, nextY);

        if(nextX < 0 || nextX >= FieldSize ||
           nextY < 0 || nextY >= FieldSize ||
                0 == mField[nextX][nextY]) return model::UNKNOWN_DIRECTION;
        const int targetX = MaxRange;
        const int targetY = MaxRange;
        while(!isNear(nextX,nextY,targetX,targetY))
        {
            const int current = mField[nextX][nextY];
            if(mField[nextX + 1][nextY] < current && 0 != mField[nextX + 1][nextY])
            {
                ++nextX;
                continue;
            }
            else if(mField[nextX - 1][nextY] < current && 0 != mField[nextX - 1][nextY])
            {
                --nextX;
                continue;
            }
            else if(mField[nextX][nextY + 1] < current && 0 != mField[nextX][nextY + 1])
            {
                ++nextY;
                continue;
            }
            else if(mField[nextX][nextY - 1] < current && 0 != mField[nextX][nextY - 1])
            {
                --nextY;
                continue;
            }
            else
            {
                return model::UNKNOWN_DIRECTION;
            }
        }
        if     (targetX > nextX) return model::WEST;
        else if(targetX < nextX) return model::EAST;
        else if(targetY > nextY) return model::NORTH;
        else                     return model::SOUTH;
    }
};

void MyStrategyImpl::Task::apply(const MyStrategyImpl* myStrategy,
                                 const model::Trooper &self,
                                 const TroopersVec &troopers,
                                 model::Move& m)
{
    m.setAction(action);
    if(0 == --count)
    {
        if(-1 != x)
        {
            m.setX(x);
            m.setY(y);
        }
        if(model::UNKNOWN_DIRECTION != dir)
        {
            m.setDirection(dir);
        }
    }
    else
    {
        switch(action)
        {
            case model::MOVE:
            {
                const PathHelper helper(myStrategy, self.getX(), self.getY(), count + 1, troopers);
                m.setDirection(helper.getDirection(x, y));
                break;
            }
            default:
            {
                if(-1 != x)
                {
                    m.setX(x);
                    m.setY(y);
                }
                if(model::UNKNOWN_DIRECTION != dir)
                {
                    m.setDirection(dir);
                }
                break;
            }
        }
    }
}

template<int MaxElements, typename Container, typename T, typename Compare>
void replaceMinElement(Container& cont, const T& item, const Compare& comp)
{
    if(cont.size() < MaxElements)
    {
        cont.push_back(item);
    }
    else
    {
        auto it = std::min_element(std::begin(cont), std::end(cont), comp);
        if(comp(*it, item)) *it = item;
    }
}

void MyStrategyImpl::createMoveTasks(
        const model::Trooper &self,
        const TroopersVec& troopers,
        const BonusesVec& bonuses,
        const model::World& world,
        const model::Game& game,
        const TrooperRestrictions& restrictions,
        TaskVecType& tasks) const
{
    if(!restrictions.canMove)
    {
        return;
    }
    const int ap = self.getActionPoints();
    const int moveCost = calcMoveCost(game, self.getStance());
    const int moveRange = ap / moveCost;
    if(moveRange > 0)
    {
        const PathHelper helper(this, self.getX(), self.getY(), moveRange, troopers);
        TaskVecType moveBonusTasks;
        for(int i = 0; i < bonuses.size(); ++i)
        {
            const model::Bonus& b = bonuses[i];
            if(model::UNKNOWN_BONUS == b.getType()) continue;
            if((model::MEDIKIT      == b.getType() && self.isHoldingMedikit()) ||
               (model::FIELD_RATION == b.getType() && self.isHoldingFieldRation()) ||
               (model::GRENADE      == b.getType() && self.isHoldingGrenade()))
            {
                continue;
            }
            if(b.getX() == self.getX() && b.getY() == self.getY()) continue;
            const int dist = helper.getDistance(b.getX(), b.getY());
            if(-1 == dist)
            {
                continue;
            }
            Task ac;
            ac.action = model::MOVE;
            ac.setCoord(b.getX(), b.getY());
            ac.bonusInd = i;
            ac.count = dist;
            ac.weight = 200;
            ac.apCost = moveCost * dist;
            moveBonusTasks.push_back(ac);
        }


//        const double shootingRange = calcShootingRange(self, game);
//        TaskVecType healMoves;
//        TaskVecType attackMoves;
//        TaskVecType attackGMoves;
//        TaskVecType tempAttackMoves;
//        TaskVecType tempAttackGMoves;
//        TaskVecType retreatMoves;
        TaskVecType advanceMoves;

        const int minX = std::max(0          , self.getX() - moveRange);
        const int maxX = std::min(mWidth - 1 , self.getX() + moveRange);
        const int minY = std::max(0          , self.getY() - moveRange);
        const int maxY = std::min(mHeight - 1, self.getY() + moveRange);
        const int moveRangeSq = moveRange * moveRange;
        for(int y = minY; y <= maxY; ++y)
        {
            for(int x = minX; x <= maxX; ++x)
            {
                const int xRange = x - self.getX();
                const int yRange = y - self.getY();
                if(0 == xRange && 0 == yRange) continue;
                if((xRange * xRange + yRange * yRange) > moveRangeSq) continue;
                const int dist = helper.getDistance(x, y);
                if(-1 == dist) continue;
                Task ac;
                ac.action = model::MOVE;
                ac.setCoord(x, y);
                ac.count = dist;
                ac.apCost = moveCost * dist;

//                replaceMinElement<MaxRetreatMoveTasks>(retreatMoves, ac, [&](const Task& ac1, const Task& ac2)
//                {
//                    return calcRetreatWeight(ac1.x, ac1.y, self.getStance()) <
//                           calcRetreatWeight(ac2.x, ac2.y, self.getStance());
//                });

//                replaceMinElement<MaxAdvanceMoveTasks>(advanceMoves, ac, [&](const Task& ac1, const Task& ac2)
//                {
//                    return calcAdvanceWeight(ac1.x, ac1.y, self.getStance()) <
//                           calcAdvanceWeight(ac2.x, ac2.y, self.getStance());
//                });
                advanceMoves.push_back(ac);
            }
        }

//        for(const auto& t : troopers)
//        {
//            if(t.getHitpoints() <= 0 || t.getId() == self.getId()) continue;
//            tempAttackMoves.clear();
//            tempAttackGMoves.clear();
//            for(int y = minY; y <= maxY; ++y)
//            {
//                for(int x = minX; x <= maxX; ++x)
//                {
//                    const int xRange = x - self.getX();
//                    const int yRange = y - self.getY();
//                    if(0 == xRange && 0 == yRange) continue;
//                    if((xRange * xRange + yRange * yRange) > moveRangeSq) continue;
//                    const int dist = helper.getDistance(x, y);
//                    if(-1 == dist) continue;
//                    Task ac;
//                    ac.action = model::MOVE;
//                    ac.setCoord(x, y);
//                    ac.count = dist;
//                    ac.apCost = moveCost * dist;

//                    if(t.isTeammate() &&
//                       t.getHitpoints() < t.getMaximalHitpoints() &&
//                       model::FIELD_MEDIC == self.getType() &&
//                       self.isHoldingMedikit() &&
//                       isNear(x, y, t.getX(), t.getY()))
//                    {
//                        healMoves.push_back(ac);
//                    }
//                    else if(!t.isTeammate() &&
//                            !world.isVisible(shootingRange,
//                                            self.getX(), self.getY(), self.getStance(),
//                                            t.getX(), t.getY(), t.getStance()) &&
//                            (ap - moveCost * dist) >= self.getShootCost() &&
//                            world.isVisible(shootingRange,
//                                            x, y, self.getStance(),
//                                            t.getX(), t.getY(), t.getStance()))
//                    {
//                        replaceMinElement<MaxAttackMoveTasks>(tempAttackMoves, ac, [](const Task& ac1, const Task& ac2)
//                        {
//                            return ac1.count < ac2.count;
//                        });
//                    }
//                    else if(self.isHoldingGrenade() &&
//                            !t.isTeammate() &&
//                            self.getDistanceTo(t) > game.getGrenadeThrowRange() &&
//                            (ap - moveCost * dist) >= game.getGrenadeThrowCost() &&
//                            t.getDistanceTo(x, y) <= game.getGrenadeThrowRange())
//                    {
//                        replaceMinElement<MaxAttackGMoveTasks>(tempAttackGMoves, ac, [](const Task& ac1, const Task& ac2)
//                        {
//                            return ac1.count < ac2.count;
//                        });
//                    }
//                }
//            }

//            std::copy(tempAttackMoves.begin(),  tempAttackMoves.end(),  std::back_inserter(attackMoves));
//            std::copy(tempAttackGMoves.begin(), tempAttackGMoves.end(), std::back_inserter(attackGMoves));
//        }

        const auto moveTasksVec = {
//            &healMoves,
//            &attackMoves,
//            &attackGMoves,
//            &retreatMoves,
            &advanceMoves
        };

        for(const auto& moveTasks : moveTasksVec)
        {
            for(const auto& t : *moveTasks)
            {
                const auto it = std::find_if(moveBonusTasks.begin(), moveBonusTasks.end(),[&](const Task& ac)
                {
                    return ac.x == t.x && ac.y == t.y;
                });
                if(moveBonusTasks.end() != it)
                {
                    (*it).weight += t.weight;
                }
                else
                {
                    tasks.push_back(t);
                }
            }
        }

        for(const auto& bt : moveBonusTasks)
        {
            tasks.push_back(bt);
        }
    }
}

MyStrategyImpl::Task MyStrategyImpl::createEndTurnTask(const model::Trooper &self,
        const TroopersVec& troopers,
        const BonusesVec& /*bonuses*/,
        const model::World& /*world*/,
        const model::Game& game,
        const model::Trooper **myTroopers,
        int endTurnPenality) const
{
    Task ac;
    ac.action = model::END_TURN;
    ac.weight = -endTurnPenality;
    StackVector<const model::Trooper*, TeamMaxTroopersCount> nearTroopers;
    //int maxDist = 0;
    for(int i = 0; i < TeamMaxTroopersCount; ++i)
    {
        if(self.getTeammateIndex() == i || 0 == myTroopers[i]) continue;
        const model::Trooper& t = *myTroopers[i];
        if(t.getHitpoints() <= 0) continue;
//        const int dist = ((t.getInitialActionPoints() - (model::STANDING - t.getStance()) * game.getStanceChangeCost()) / game.getStandingMoveCost());
//        if(self.getDistanceTo(t) <= dist)
//        {
//            nearTroopers.push_back(&t);
//        }
        //maxDist = std::max(maxDist, (t.getInitialActionPoints() - (model::STANDING - t.getStance()) * game.getStanceChangeCost()) / game.getStandingMoveCost());
        nearTroopers.push_back(&t);
    }

    // Select Role
    TrooperRole role = RoleWander;
    const model::Trooper* leader = 0;
    if(nearTroopers.size() > 0)
    {
        int maxStr = std::numeric_limits<int>::min();
        int maxStrI = 0;
        for(int i = 0 ; i < nearTroopers.size(); ++i)
        {
            if(calcTrooperStrength(*nearTroopers[i]) > maxStr)
            {
                maxStr = calcTrooperStrength(*nearTroopers[i]);
                maxStrI = i;
            }
        }
        if(calcTrooperStrength(self) > maxStr)
        {
            role = RoleLeader;
        }
        else
        {
            role = RoleSupport;
            leader = nearTroopers[maxStrI];
        }
    }

    const int CommanderNearBonus = 300;
    const double SupportMinDist  = 1.5;
    const double SupportMaxDist  = 5.0;
    const int SupportNearBonus   = 2000;

    if(RoleLeader == role)
    {
        ac.weight += getCell(self.getX(), self.getY()).weight;
        for(int i = 0; i < nearTroopers.size(); ++i)
        {
            const model::Trooper& t = *nearTroopers[i];
            const int dist = ((t.getInitialActionPoints() - (model::STANDING - t.getStance()) * game.getStanceChangeCost()) /
                    game.getStandingMoveCost());
            const PathHelper helper(this, t.getX(), t.getY(), dist, troopers);

            if(-1 != helper.getDistance(self.getX(), self.getY()))
            {
                ac.weight += CommanderNearBonus;
            }
        }
    }
    else if(RoleSupport == role)
    {
        const PathHelper helper(this, self.getX(), self.getY(), 10);
        const int dist = helper.getDistance(leader->getX(), leader->getY());
        //LOG() << "dist: " << dist << std::endl;
        if(-1 != dist)
        {
            const double coeff = 1.0 - std::abs((double)dist - SupportMinDist) / SupportMaxDist;
            ac.weight += SupportNearBonus * coeff/* * getCell(self.getX(), self.getY()).unknownDanger[self.getStance()]*/;
            ac.weight -= getCell(self.getX(), self.getY()).weight / 5;
        }
        else
        {
            ac.weight += getCell(self.getX(), self.getY()).weight;
        }
    }
    else
    {
        ac.weight += getCell(self.getX(), self.getY()).weight;
    }
    ac.weight -= getCell(self.getX(), self.getY()).enemyDanger;
    ac.weight -= getCell(self.getX(), self.getY()).unknownDanger[self.getStance()] * UnknownDangerMult;
//        LOG() << "Role: " << role
//              << " type: " << self.getType()
//              << " wght:" << ac.weight
//              << " near: " << nearTroopers.size() << std::endl;
    return ac;
}

template<int MaxResultCount>
StackVector<MyStrategyImpl::TaskVecType, MaxResultCount> MyStrategyImpl::createTasks(
        const model::Trooper& self,
        const TroopersVec& troopers,
        const BonusesVec& bonuses,
        const model::World& world,
        const model::Game& game,
        int depth,
        const TrooperRestrictions &restrictions)
{
//    ++mCreateCount;

    const int ap = self.getActionPoints();
    const model::Trooper* myTroopers[TeamMaxTroopersCount] = {0};
    myTroopers[self.getTeammateIndex()] = &self;
    TaskVecType tasks;
    int endTurnPenality = -10;
    const double shootingRange = calcShootingRange(self, game);
    for(int i = 0; i < troopers.size(); ++i)
    {
        const model::Trooper& t = troopers[i];

        if(t.getTeammateIndex() != self.getTeammateIndex() &&
           t.isTeammate() && t.getHitpoints() > 0 &&
           !contains(restrictions.ignoreSoldier, i))
        {
            myTroopers[t.getTeammateIndex()] = &t;
            //check heal
            if(t.getHitpoints() < t.getMaximalHitpoints())
            {
                if((self.isHoldingMedikit() && ap >= game.getMedikitUseCost()))
                {
                    if(isNear(self.getX(), self.getY(), t.getX(), t.getY()))
                    {
                        const int restore = std::min(t.getMaximalHitpoints() - t.getHitpoints(), game.getMedikitBonusHitpoints());
                        Task ac;
                        ac.action = model::USE_MEDIKIT;
                        if     (t.getX() > self.getX()) ac.dir = model::EAST;
                        else if(t.getX() < self.getX()) ac.dir = model::WEST;
                        else if(t.getY() > self.getY()) ac.dir = model::SOUTH;
                        else                            ac.dir = model::NORTH;

                        ac.weight = HealingScoreMult * restore - BonusUsePenality;
                        ac.trooperInd = i;
                        ac.apCost = game.getMedikitUseCost();
                        tasks.push_back(ac);
                    }
                }
                if(model::FIELD_MEDIC == self.getType() &&
                   ap >= game.getFieldMedicHealCost())
                {
                    if(isNear(self.getX(), self.getY(), t.getX(), t.getY()))
                    {
                        const int myHeal = game.getFieldMedicHealBonusHitpoints();
                        const int maxCount = std::min(ap / game.getFieldMedicHealCost(),
                                                      (t.getMaximalHitpoints() - t.getHitpoints() + myHeal - 1) / myHeal);
                        for(int cnt = 1; cnt <= maxCount; ++cnt)
                        {
                            const int restore = std::min(t.getMaximalHitpoints() - t.getHitpoints(), myHeal * cnt);
                            Task ac;
                            ac.action = model::HEAL;
                            ac.count = cnt;
                            if     (t.getX() > self.getX()) ac.dir = model::EAST;
                            else if(t.getX() < self.getX()) ac.dir = model::WEST;
                            else if(t.getY() > self.getY()) ac.dir = model::SOUTH;
                            else                            ac.dir = model::NORTH;

                            ac.weight = HealingScoreMult * restore;
                            ac.trooperInd = i;
                            ac.apCost = game.getFieldMedicHealCost() * cnt;
                            tasks.push_back(ac);
                        }
                    }
                }
            }
        }

        if(!t.isTeammate() &&
           t.getHitpoints() > 0)
        {
            if(!contains(restrictions.ignoreSoldier, i) &&
               ap >= self.getShootCost() &&
               world.isVisible(shootingRange,
                               self.getX(), self.getY(), self.getStance(),
                               t.getX()   , t.getY()   , t.getStance()))
            {
                const int myDmg = calcDamage(self, self.getStance());
                const int maxCount = std::min(ap / self.getShootCost(),
                                              (t.getHitpoints() + myDmg - 1) / myDmg);
                for(int cnt = 1; cnt <= maxCount; ++cnt)
                {
                    Task ac;
                    ac.action = model::SHOOT;
                    ac.setCoord(t.getX(), t.getY());
                    ac.trooperInd = i;
                    ac.count = cnt;
                    const int dmg = std::min(t.getHitpoints(), myDmg * cnt);
                    ac.weight = dmg * AttackScoreMult;
                    if(t.getHitpoints() <= dmg)
                    {
                        ac.weight += game.getTrooperEliminationScore() * AttackScoreMult;
                    }
                    ac.weight += calcTrooperStrength(t);
                    ac.apCost = self.getShootCost() * cnt;
                    tasks.push_back(ac);
                }
            }

            if(self.isHoldingGrenade() &&
               ap >= game.getGrenadeThrowCost() &&
               self.getDistanceTo(t) < game.getGrenadeThrowRange())
            {
                Task ac;
                ac.action = model::THROW_GRENADE;
                ac.setCoord(t.getX(), t.getY());
                ac.trooperInd = i;
                const int dmg = std::min(t.getHitpoints(), game.getGrenadeDirectDamage());
                ac.weight = dmg * AttackScoreMult - BonusUsePenality;
                if(t.getHitpoints() <= dmg)
                {
                    ac.weight += game.getTrooperEliminationScore() * AttackScoreMult;
                }
                ac.weight += calcTrooperStrength(t);
                for(int j = 0; j < troopers.size(); ++j)
                {
                    if(i == j) continue;
                    const model::Trooper& t1 = troopers[j];
                    if(isNear(t1.getX(), t1.getY(), t.getX(), t.getY()))
                    {
                        const int cdmg = std::min(t1.getHitpoints(), game.getGrenadeCollateralDamage());
                        int bonus = cdmg * AttackScoreMult;
                        if(t1.getHitpoints() <= cdmg)
                        {
                            bonus += AttackScoreMult;
                        }
                        bonus += calcTrooperStrength(t1);
                        if(t.isTeammate()) bonus = -bonus;
                        ac.weight += bonus;
                    }
                }
                ac.apCost = game.getGrenadeThrowCost();
                tasks.push_back(ac);
            }

            int penality = 0;
            int atkK = -1;
            for(int k = -1; k < troopers.size(); ++k)
            {
                const model::Trooper& t1 = (-1 == k ? self : troopers[k]);
                if(t.getPlayerId() == t1.getPlayerId() || t1.getHitpoints() <= 0) continue;
                if(t1.getTeammateIndex() == self.getTeammateIndex()) continue;
                int dmg = 0;
                int enemyAp = t.getInitialActionPoints();
                if(t.isHoldingGrenade() &&
                   t.getDistanceTo(t1) <= game.getGrenadeThrowRange())
                {
                    dmg += game.getGrenadeDirectDamage();
                    enemyAp -= game.getGrenadeThrowCost();
                }
                const int moveCost = calcMoveCost(game, t.getStance());
                int rangeInc = enemyAp / moveCost;
                for(int j = 0; j <= rangeInc; ++j)
                {
                    const int remAp = enemyAp - j * moveCost;
                    if(remAp < t.getShootCost()) break;
                    if(world.isVisible(calcShootingRange(t, game) + j,
                                       t.getX(), t.getY(), t.getStance(),
                                       t1.getX(), t1.getY(), t1.getStance()))
                    {
                        const int shotCount = remAp / t.getShootCost();
                        dmg += calcDamage(t, t.getStance()) * shotCount;
                        break;
                    }
                }

                dmg = std::min(dmg, t1.getHitpoints());
                int tempPenanlity = dmg;
                if(dmg >= t1.getHitpoints())
                {
                    tempPenanlity += game.getTrooperEliminationScore();
                }

                if(tempPenanlity > penality)
                {
                    atkK = k;
                    penality = tempPenanlity;
                }
            }
            if(-1 == atkK)
            {
                endTurnPenality += penality * EnemyAttackScoreMultSelf;
            }
            else if(troopers[atkK].isTeammate())
            {
                endTurnPenality += penality * EnemyAttackScoreMult;
            }

            if(!world.isVisible(t.getVisionRange(),
                                t.getX(),    t.getY(),    t.getStance(),
                                self.getX(), self.getY(), self.getStance()))
            {
                endTurnPenality -= OutOfEnemySightBonus;
            }
        }
    }

    if(self.isHoldingFieldRation() &&
       ap >= game.getFieldRationEatCost() &&
       self.getActionPoints() < self.getInitialActionPoints())
    {
        Task ac;
        ac.action = model::EAT_FIELD_RATION;
        ac.weight = -BonusUsePenality;
        ac.apCost = game.getFieldRationEatCost();
        tasks.push_back(ac);
    }

    if(self.getActionPoints() >= game.getStanceChangeCost())
    {
        if(model::STANDING != self.getStance() && restrictions.canRaiseStance)
        {
            Task ac;
            ac.action = model::RAISE_STANCE;
            ac.weight = 0;
            ac.apCost = game.getStanceChangeCost();
            tasks.push_back(ac);
        }
        if(model::PRONE != self.getStance() && restrictions.canLowerStance)
        {
            Task ac;
            ac.action = model::LOWER_STANCE;
            ac.weight = 0;
            ac.apCost = game.getStanceChangeCost();
            tasks.push_back(ac);
        }
    }

    if(self.getHitpoints() < self.getMaximalHitpoints())
    {
        if(self.isHoldingMedikit() &&
           (ap >= game.getMedikitUseCost()))
        {
            const int restore = std::min(self.getMaximalHitpoints() - self.getHitpoints(), game.getMedikitHealSelfBonusHitpoints());
            Task ac;
            ac.action = model::USE_MEDIKIT;
            ac.dir = model::CURRENT_POINT;
            ac.weight = HealingScoreMult * restore - BonusUsePenality;
            ac.trooperInd = -1;
            ac.apCost = game.getMedikitUseCost();
            tasks.push_back(ac);
        }
        if((model::FIELD_MEDIC == self.getType()) &&
           (ap >= game.getFieldMedicHealCost()) &&
            restrictions.canHealSelf)
        {
            const int myHeal = game.getFieldMedicHealSelfBonusHitpoints();
            const int maxCount = std::min(ap / game.getFieldMedicHealCost(),
                                          (self.getMaximalHitpoints() - self.getHitpoints() + myHeal - 1) / myHeal + 1);
            for(int cnt = 1; cnt <= maxCount; ++cnt)
            {
                const int restore = std::min(self.getMaximalHitpoints() - self.getHitpoints(), myHeal * cnt);
                Task ac;
                ac.action = model::HEAL;
                ac.dir = model::CURRENT_POINT;
                ac.weight = HealingScoreMult * restore;
                ac.trooperInd = -1;
                ac.apCost = game.getFieldMedicHealCost() * cnt;
                ac.count = cnt;
                tasks.push_back(ac);
            }
        }
    }

    createMoveTasks(self, troopers, bonuses, world, game, restrictions, tasks);

    if(model::COMMANDER == self.getType() &&
       MaxPathWeight == getCell(self.getX(), self.getY()).weight &&
       ap >= game.getCommanderRequestEnemyDispositionCost())
    {
        Task ac;
        ac.action = model::REQUEST_ENEMY_DISPOSITION;
        ac.weight = RequestEnenmyDispBonus;
        ac.apCost = game.getCommanderRequestEnemyDispositionCost();
        tasks.push_back(ac);
    }

    tasks.push_back(createEndTurnTask(self, troopers, bonuses, world, game, myTroopers, endTurnPenality));

    StackVector<TaskVecType, MaxResultCount> ret;
    if(depth > 0)
    {
        for(const auto& ac : tasks)
        {
            StackVector<TaskVecType, MaxResultCount> tempContainer;

            bool reject = false;
            applyTask(ac, self, bonuses, troopers, world, game,
                      [&](const model::Trooper& self1,
                      const BonusesVec &bonuses1,
                      const TroopersVec &troopers1,
                      const model::World& world1,
                      const model::Game& game1,
                      bool isEnd)
            {
                if(isEnd)
                {
                    tempContainer.clear();
                    ++(this->mCreateCount);
                }
                else
                {
                    const auto h = calcHash(self1, troopers1, bonuses1);
                    if(contains(restrictions.movesToReject, h))
                    {
                        reject = true;
                        return;
                    }
                    TrooperRestrictions res = restrictions;
                    res.canMove        = (ac.action != model::LOWER_STANCE && ac.action != model::MOVE) || -1 != ac.bonusInd;
                    res.canRaiseStance = ac.action != model::LOWER_STANCE;
                    res.canLowerStance = ac.action != model::RAISE_STANCE;
                    res.canHealSelf    = false;
                    res.movesToReject.insert(h);
                    if(-1 != ac.trooperInd)
                    {
                        res.ignoreSoldier.insert(ac.trooperInd);
                    }
                    tempContainer = this->createTasks<MaxResultCount>(self1,
                                                                      troopers1,
                                                                      bonuses1,
                                                                      world1,
                                                                      game1,
                                                                      depth - 1,
                                                                      res);
                }
            });

            if(reject)
            {
                ++mRejectCount;
                continue;
            }

            for(int j = 0; j < tempContainer.size(); ++j)
            {
                TaskVecType& c = tempContainer[j];
                Task lastTask = ac;
                if(!c.empty())
                {
                    lastTask.weight += c.back().weight;
                }
                c.push_back(lastTask);
                if(ret.size() < MaxResultCount)
                {
                    ret.push_back(c);
                }
                else
                {
                    int minWeight = std::numeric_limits<int>::max();
                    int minK = 0;
                    for(int k = 0; k < ret.size(); ++k)
                    {
                        const int wght = ret[k].back().weight;
                        if(wght < minWeight)
                        {
                            minWeight = wght;
                            minK = k;
                        }
                    }
                    if(lastTask.weight > minWeight)
                    {
                        std::swap(c, ret[minK]);
                    }
                }
            }
        }
    }

    if(ret.empty())
    {
        ret.push_back(TaskVecType());
    }

    if(ret.front().empty())
    {
        ret.front().push_back(*std::max_element(tasks.begin(), tasks.end()));
    }
    return ret;
}

template<int MaxCount>
MyStrategyImpl::TaskVecType MyStrategyImpl::calc(
        const model::Trooper& self,
        const TroopersVec& troopers,
        const BonusesVec& bonuses,
        const model::World& world,
        const model::Game& game,
        int depth)
{
    if(MaxCount > 1 && depth > 1 && mCreateCount < BruteForceThreshhold)
    {
        const auto strategies = createTasks<MaxCount>(self, troopers, bonuses, world, game, MaxSimulationDepth);
        for(auto& tasks : strategies)
        {
            const auto funcPtr = (mCreateCount < BruteForceThreshhold / 2) ?
                        &MyStrategyImpl::calc<MaxCount> :
                        &MyStrategyImpl::calc<MaxCount/2>;
            applyTasks(tasks, self, bonuses, troopers, world, game, [&](const model::Trooper& self1,
                       const BonusesVec& bonuses1,
                       const TroopersVec& troopers1,
                       const model::World& world1,
                       const model::Game& game1)
           {
               //TO DO: apply enemy moves
               TroopersVec newTroopers = troopers1;
               model::Trooper* myTroopers[TeamMaxTroopersCount] = {0};
               for(auto& t : newTroopers)
               {
                   if(t.isTeammate() && t.getHitpoints() > 0) myTroopers[t.getTeammateIndex()] = &t;
               }
               int next = self1.getTeammateIndex();
               while(true)
               {
                   next = getNextTrooper(next);
                   if(next == self1.getTeammateIndex() || 0 != myTroopers[next]) break;
               }
               if(next == self1.getTeammateIndex())
               {
                   return;
               }
               *myTroopers[self1.getTeammateIndex()] = self1;
               model::Trooper newSelf = *myTroopers[next];
               newSelf.actionPoints = newSelf.getInitialActionPoints();
               //calc<MaxCount/2>(newSelf, newTroopers, bonuses1, world1, game1, depth - 1); //ICE on mingw 4.7.2
               tasks.back().weight += (this->*funcPtr)(newSelf, newTroopers, bonuses1, world1, game1, depth - 1).back().weight;
           });
        }

        return *std::max_element(strategies.begin(), strategies.end(),
                                 [](const TaskVecType& a, const TaskVecType& b)
        {
            return a.back().weight < b.back().weight;
        });
    }
    else
    {
        return createTasks<1>(self, troopers, bonuses, world, game, MaxSimulationDepth).front();
    }
}

MyStrategyImpl::MyStrategyImpl():
    mInit(false),
    mStartCorner(0),
    mWidth(0),
    mHeight(0),
    mNeedUpdateMap(true),
    mLastMapUpdate(-1),
    mMapUpdateCount(0),
    mNeedUpdate(true),
    mUpdateCount(0),
    mDispositionReq(false),
    mCells(0)
{
    //srand(time(NULL));
    mStats[model::COMMANDER].aggresion = 2.5f;
    mStats[model::COMMANDER].defence   = 1.5f;
    mStats[model::COMMANDER].support   = 1.0f;
    mStats[model::COMMANDER].teamWork  = 2.0f;

    mStats[model::FIELD_MEDIC].aggresion = 1.0f;
    mStats[model::FIELD_MEDIC].defence   = 10.0f;
    mStats[model::FIELD_MEDIC].support   = 10.0f;
    mStats[model::FIELD_MEDIC].teamWork  = 3.0f;

    mStats[model::SOLDIER].aggresion = 3.0f;
    mStats[model::SOLDIER].defence   = 1.5f;
    mStats[model::SOLDIER].support   = 1.0f;
    mStats[model::SOLDIER].teamWork  = 1.0f;
}

MyStrategyImpl::~MyStrategyImpl()
{
    delete[] mCells;
}

void MyStrategyImpl::move(const model::Trooper& self, const model::World& world, const model::Game& game, model::Move& move)
{
    //LOGL() << "move: " << self.getType();
    if(!mInit)
    {
        firstInit(self, world, game);
        mInit = true;
    }

    if(model::COMMANDER == self.getType() && mDispositionReq)
    {
        mNeedUpdateMap = true;
        mNeedUpdate = true;
    }

    if(mNeedUpdateMap)
    {
        updateMap(self, world, game);
        mDispositionReq = false;
        mNeedUpdateMap = false;
        mLastMapUpdate = world.getMoveIndex();
    }

    TroopersVec troopers = world.getTroopers();
    BonusesVec  bonuses  = world.getBonuses();

    if(mMoveOrder.end() == std::find(mMoveOrder.begin(), mMoveOrder.end(), self.getTeammateIndex()))
    {
        mMoveOrder.push_back(self.getTeammateIndex());
    }

    const int newTeammateIndex = self.getTeammateIndex();
    if(newTeammateIndex != mPreviousTeammate)
    {
        mPreviousTroopers.clear();
        mNeedUpdate = true;
        mPreviousTeammate = newTeammateIndex;
    }

    //add ghost troopers
    for(const auto& t2 : mPreviousTroopers)
    {
        if(!t2.isTeammate() &&
           t2.getHitpoints() > 0 &&
           troopers.end() == std::find_if(troopers.begin(), troopers.end(),
            [&](const model::Trooper& t)
            {
                return t.getPlayerId()      == t2.getPlayerId() &&
                       t.getTeammateIndex() == t2.getTeammateIndex();
            }))
        {
            troopers.push_back(t2);
        }
    }

    if(troopers.size() > mPreviousTroopers.size() ||
       mActions.empty())
    {
        mNeedUpdate = true;
    }

    if(mNeedUpdate)
    {
        //LOG() << "update" << std::endl;
        //const bool inFight = isInFight(self, troopers);
        //const int teamSize = getTeamSize(self, troopers);
        updateDanger(self, world, game);
        mCreateCount = 0;
        mRejectCount = 0;
        mActions = createTasks<1>(self, troopers, bonuses, world, game, MaxSimulationDepth).front();
        //mActions = calc(self, troopers, bonuses, world, game, 2);
//        static int minCnt = 900000000;
//        static int maxCnt = 0;
//        static float avgCnt = 0.0f;
//        minCnt = std::min(minCnt, mCreateCount);
//        maxCnt = std::max(maxCnt, mCreateCount);
//        avgCnt = (avgCnt + mCreateCount) / 2.0;
//        LOGL() << "Create count: " << mCreateCount << " "<< mRejectCount;
//        LOGL() << "Reject count: " << mRejectCount;
//        LOGL() << "min: " << minCnt;
//        LOGL() << "max: " << maxCnt;
//        LOGL() << "avg: " << avgCnt;
        mNeedUpdate = false;
    }

    Task& ac = mActions.back();
    ac.apply(this, self, troopers, move);
//    applyTask(ac, self, bonuses, troopers, world, game,
//              [&](const model::Trooper& self1,
//                  const BonusesVec& bonuses1,
//                  const TroopersVec& troopers1,
//                  const model::World& world1,
//                  const model::Game& game1,
//                  bool isEnd)
//    {
//        troopers = troopers1;
//    });
//    LOG() << "\t" << "Task::apply" << std::endl;
//    LOG() << "\t" << "self: " << self.getX() << " " << self.getY() << std::endl;
//    LOG() << "\t" << ac.action << " " << ac.x << " " << ac.y << std::endl;
//    LOG() << "\t" << move.getX() << " " << move.getY() << " " << move.getDirection() << std::endl;
    if(model::REQUEST_ENEMY_DISPOSITION == ac.action)
    {
        mDispositionReq = true;
    }

    if(0 == ac.count)
    {
        mActions.pop_back();
    }
    std::swap(troopers, mPreviousTroopers);
    std::swap(bonuses,  mPreviousBonuses);
}

MyStrategyImpl MyStrategyImpl::instance;
